#include "NodeEnumerator.h"

NodeEnumerator::NodeEnumerator(FILE * output, const char * basePath, const char * sceneName) : _output(output), _tabLevel(1), _basePath(basePath), _sceneName(sceneName)
{
}

NodeEnumerator::~NodeEnumerator()
{
}

void NodeEnumerator::writeTabs(int level)
{
	if(level < 0)
		level = _tabLevel;

	for(int i = 0; i < level; i++)
		fwrite("\t", 1, 1, _output);
}

void NodeEnumerator::writeString(const std::string & string, int level)
{
	// write tabs
	writeTabs(level);

	// write text
	fputs(string.c_str(), _output);
}

int NodeEnumerator::parseNode(IGameNode * node)
{
	// convert to game node
	// node attributes
	std::ostringstream positionAttribute;
	std::ostringstream rotationAttribute;
	std::ostringstream scaleAttribute;
	std::ostringstream sourceAttribute;

	// parse attributes
	// getting matrix
	Matrix3 localMatrix = node->GetMaxNode()->GetNodeTM(0);
	if(node->GetMaxNode()->GetParentNode() != NULL)
		localMatrix = localMatrix * Inverse(node->GetMaxNode()->GetParentNode()->GetNodeTM(0));

	// getting transform
	Point3 tranlate = localMatrix.GetTrans();
	Point3 scale    = Point3(localMatrix.GetRow(0).Length(), localMatrix.GetRow(1).Length(), localMatrix.GetRow(2).Length());
	Point3 rotation;
	localMatrix.GetYawPitchRoll(&rotation.y, &rotation.x, &rotation.z);
	
	// make strings
	/*
	if(!tranlate.Equals(Point3(0.0f, 0.0f, 0.0f)))
		positionAttribute << " Position=\"" << tranlate.x << " " << tranlate.y << " " << tranlate.z << "\"";

	if(!scale.Equals(Point3(1.0f, 1.0f, 1.0f)))
		scaleAttribute << " Scale=\"" << scale.x << " " << scale.y << " " << scale.z << "\"";

	if(!rotation.Equals(Point3(0.0f, 0.0f, 0.0f)))
		rotationAttribute << " Rotation=\"" << rotation.x << " " << rotation.y << " " << rotation.z << "\"";
	*/
	// getting node type
	std::string meshPath = "";
	std::string nodeName = "Node";

	// check if its a mesh node
	if(node->GetIGameObject()->GetIGameType() == IGameObject::IGAME_MESH)
	{
		// save node name
		nodeName = "ModelFixed";

		meshPath = StringToLower(_sceneName + ".geom/" + node->GetName() + ".mesh");

		// create source attribute buffer
		sourceAttribute << " Source=\"" << meshPath << "\"";

		// save mesh path
		meshPath = StringToLower(_basePath + meshPath);
	}

	// create node entry text
	std::ostringstream nodeElement;
	nodeElement << "<" << nodeName << " Name=\"" << node->GetName() << "\"" << positionAttribute.str() << rotationAttribute.str() << scaleAttribute.str() << sourceAttribute.str() << ">\n";

	// write node entry
	writeString(nodeElement.str());

	// increase tabs level
	_tabLevel++;

	// parse mesh
	if(node->GetIGameObject()->GetIGameType() == IGameObject::IGAME_MESH)
		parseMesh(meshPath, (IGameMesh*)node->GetIGameObject(), node->GetNodeMaterial(), Inverse(node->GetMaxNode()->GetNodeTM(0)));

	// parse sub-nodes
	for(int i = 0; i < node->GetChildCount(); i++)
		parseNode(node->GetNodeChild(i));

	// decrease tabs level
	_tabLevel--;

	// close node entry
	writeString("</" + nodeName + ">\n");

	// all done
	return 1;
}

void NodeEnumerator::parseMesh(const std::string & path, IGameMesh * mesh, IGameMaterial * masterMaterial, Matrix3 globalMatrix)
{
	// validate mesh
	if(mesh == NULL)
		return;

	// create output directory for meshes
	CreateDirectory(GetPathFromFileName(path).c_str(), NULL);

	// initialize mesh
	mesh->InitializeData();

	// buffers for mesh data
	std::vector<float> positions;
	std::vector<float> normals;
	std::vector<float> uvs;
	std::vector<float> colors;
	std::vector<int>   indices;

	// create normals
	mesh->SetCreateOptimizedNormalList();

	// getting all materials
	Tab<int> materialIDs = mesh->GetActiveMatIDs();
	
	// starting index for previous material
	size_t startIndex      = 0;
	size_t unmappedIndices = 0;

	// write shaders element
	writeString("<Shaders>\n");

	// parse triangles for each material
	for(int i = 0; i < materialIDs.Count(); i++)
	{
		// getting all triangles for this material
		Tab<FaceEx*> triangles = mesh->GetFacesFromMatID(materialIDs[i]);

		// add indices to buffer
		for(int j = 0; j < triangles.Count(); j++)
		{
			// getting tirangle
			FaceEx * current = triangles[j];

			// push vertex data
			for(int k = 0; k < 3; k++)
			{
				// push index
				indices.push_back((int)(positions.size() / 3));

				// getting vertex data
				Point3 position = mesh->GetVertex(current->vert[k], false);
				Point3 normal   = mesh->GetNormal(current->norm[k], false);
				Point2 uv       = mesh->GetTexVertex(current->texCoord[k]);
				Point3 color    = mesh->GetColorVertex(current->color[k]);
				float  alpha    = mesh->GetAlphaVertex(current->alpha[k]);

				// transfrom position and normal
				//position = globalMatrix.PointTransform(position);
				//normal   = globalMatrix.VectorTransform(normal);

				// push it to buffers
				// position
				positions.push_back(position.x);
				positions.push_back(position.y);
				positions.push_back(position.z);

				// normal
				normals.push_back(normal.x);
				normals.push_back(normal.y);
				normals.push_back(normal.z);

				// uv
				uvs.push_back(uv.x);
				uvs.push_back(1.0f - uv.y);

				// color
				colors.push_back(color.x);
				colors.push_back(color.y);
				colors.push_back(color.z);
				colors.push_back(alpha);
			}
		}

		// compute triangles for this material
		size_t trianglesCount = (indices.size() - startIndex) / 3;

		// save current index
		startIndex = indices.size();

		// parse material
		if(masterMaterial != NULL)
		{
			// write default material if needed
			if(unmappedIndices > 0)
			{
				writeDefaultMaterial(unmappedIndices);
				unmappedIndices = 0;
			}

			// parse material for this node
			if(masterMaterial->GetSubMaterialCount() > 0)
			{
				parseMaterial(trianglesCount, masterMaterial->GetSubMaterial(materialIDs[i]));
			}
			else
			{
				parseMaterial(trianglesCount, masterMaterial);
			}
		}
		else
		{
			unmappedIndices += trianglesCount;
		}
	}

	// write default material if needed
	if(unmappedIndices > 0)
	{
		writeDefaultMaterial(unmappedIndices);
		unmappedIndices = 0;
	}

	// close shaders element
	writeString("</Shaders>\n");

	// open mesh file
	FILE * meshFile = fopen(path.c_str(), "wb");
	if(meshFile == NULL)
		return;

	// buffers sizes
	int positionsSize = (int)positions.size();
	int normalsSize   = (int)normals.size();
	int uvsSize       = (int)uvs.size();
	int colorsSize    = (int)colors.size();
	int indicesSize   = (int)indices.size();

	// write buffers
	// positions
	fwrite(&positionsSize, sizeof(int), 1, meshFile);
	if(positionsSize > 0)
		fwrite(&positions[0], sizeof(float), positionsSize, meshFile);

	// normals
	fwrite(&normalsSize, sizeof(int), 1, meshFile);
	if(normalsSize > 0)
		fwrite(&normals[0], sizeof(float), normalsSize, meshFile);

	// uvs
	fwrite(&uvsSize, sizeof(int), 1, meshFile);
	if(uvsSize > 0)
		fwrite(&uvs[0], sizeof(float), uvsSize, meshFile);

	// colors
	fwrite(&colorsSize, sizeof(int), 1, meshFile);
	if(colorsSize > 0)
		fwrite(&colors[0], sizeof(float), colorsSize, meshFile);

	// indices
	fwrite(&indicesSize, sizeof(int), 1, meshFile);
	if(indicesSize > 0)
		fwrite(&indices[0], sizeof(int), indicesSize, meshFile);

	// close mesh file
	fclose(meshFile);
}

void NodeEnumerator::writeDefaultMaterial(size_t trianglesCount)
{
	// write shader node entry
	std::ostringstream shaderElement;
	shaderElement << "<Shader Name=\"Default\" TrianglesCount=\"" << trianglesCount << "\">\n";
	writeString(shaderElement.str(), _tabLevel + 1);

	// write material node
	writeString("<Material SpecularPower=\"0\" Diffuse=\"1 1 1\" Ambient=\"0.5 0.5 0.5\" Specular=\"1 1 1\" />\n", _tabLevel + 2);

	// close shader node
	writeString("</Shader>\n", _tabLevel + 1);
}

void NodeEnumerator::parseMaterial(size_t trianglesCount, IGameMaterial * material)
{
	// validate material
	if(material == NULL)
		return;

	// getting material properties
	float  specularPower = 0.0f;
	Point3 diffuseColor;
	Point3 ambientColor;
	Point3 specularColor;

	material->GetSpecularLevelData()->GetPropertyValue(specularPower);
	material->GetDiffuseData()->GetPropertyValue(diffuseColor);
	material->GetAmbientData()->GetPropertyValue(ambientColor);
	material->GetSpecularData()->GetPropertyValue(specularColor);

	// write shader node entry
	std::ostringstream shaderElement;
	shaderElement << "<Shader Name=\"" << material->GetMaterialName() << "\" TrianglesCount=\"" << trianglesCount << "\">\n";
	writeString(shaderElement.str(), _tabLevel + 1);

	// write material node
	std::ostringstream materialElement;
	materialElement << "<Material SpecularPower=\"" << specularPower << "\" Diffuse=\"" << diffuseColor.x << " " << diffuseColor.y << " " << diffuseColor.z;
	materialElement << "\" Ambient=\"" << ambientColor.x << " " << ambientColor.y << " " << ambientColor.z;
	materialElement << "\" Specular=\"" << specularColor.x << " " << specularColor.y << " " << specularColor.z << "\" />\n";
	writeString(materialElement.str(), _tabLevel + 2);

	// parse texture
	if(material->GetNumberOfTextureMaps() > 0)
		parseTexture(material->GetIGameTextureMap(0));

	// close shader node
	writeString("</Shader>\n", _tabLevel + 1);
}

void NodeEnumerator::parseTexture(IGameTextureMap * texture)
{
	// validate texture
	if(texture == NULL)
		return;

	// make engine's texture path
	std::string texturePath = StringToLower(_sceneName + ".textures/" + GetFileNameFromPath(texture->GetBitmapFileName()));

	// create output directory for textures if needed
	CreateDirectory(GetPathFromFileName(texturePath).c_str(), NULL);

	// move texture
	CopyFile(texture->GetBitmapFileName(), (texturePath + GetFileExtensionFromPath(texture->GetBitmapFileName())).c_str(), FALSE);

	// write texture entry
	std::ostringstream textureElement;
	textureElement << "<Diffuse Image=\"" << texturePath << "\" />\n";
	writeString(textureElement.str(), _tabLevel + 2);
}